/*
 * Copyright (C) 2014 - 2015 Jenia Software.
 *
 * This file is part of Sinekarta-ds
 *
 * Sinekarta-ds is Open SOurce Software: you can redistribute it and/or modify
 * it under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sinekartads.smartcard;

import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.sinekartads.exception.CertificateListException;
import org.sinekartads.exception.InvalidPKCS11DriverException;
import org.sinekartads.exception.InvalidPinException;
import org.sinekartads.exception.InvalidSmartCardException;
import org.sinekartads.exception.PKCS11DriverNotFoundException;
import org.sinekartads.exception.PinLockedException;
import org.sinekartads.exception.SmartCardAccessException;
import org.sinekartads.exception.SmartCardReaderNotFoundException;
import org.sinekartads.utils.X509Utils;

public class FakeSmartCardAccess implements ISmartCardAccess {

	public static final byte[] SHA256_DIGEST_INFO_PREFIX = new byte[] { 0x30,
		0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65,
		0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
	
	private static final Logger tracer = Logger.getLogger(SmartCardAccess.class);
	public static String FAKE_DRIVER = "fake";
	public static String FAKE_PIN	 = "123";
	
	Map<String, String> privateKeys = new HashMap<String, String>();
	Map<String, String> certChains  = new HashMap<String, String>();
	PrivateKey privateKey;

	public FakeSmartCardAccess ( ) {
		privateKeys.put("SineKarta", "308204be020100300d06092a864886f70d0101010500048204a8308204a402010002820101009ff3206f7a3eb56f8a5ae3bf2345187f610979da1417f9c7890e17b27be49e50304f680cfa6bcac63f22c416b3551f8c3f98316b62b901aae4afb30d228519dba00ed428ddb6df7569a348cc2e33a221bf86cbfe8e9a8bba76ae2e8c34a19aa2c3b681cbe7383198727b2d119404ff5d857743ca81936f51f5fb77c338e1e1b686945320865f08b62321b4cf7c2f687aaedac653b0e9164861349f9bf7f27f9b0ab2383c534f4d835f1842f6ba3839490765abccb27d5d64107af51ec84fae879977a658e5ae4b4d97b5c3c11948311e79652a30dd2818d78d2af620fd7da2c62b47166a8805313523f340d65b2e9b646dcb9acded010a2d4d6a311b39a4437b0203010001028201004715c0468ae7afaee0a8402b443b8305a95492c8eec1310b12c82693f627c68c10cc598bdb0768d8ee5a3f176f9fd7237767e61eb9834b506dce001f48462b15c66e90af90c9a34a0526c245ac0ada75712f311df2b6907ed01bdd28068403bb6e8b7fe7e4dcbe7de7f302369bb814ddb56129c9a808a422a2d59d862f9383c1ebbbba4f8c4d5f275d746f3d138ff30fa7634c40aecfcb8566039f54d07e194ce02ef192d74d6e7ce97d2193bdff6b57e1a7303e107e7873aa17bcb46af90f60d988d6f56fd3371185acfab4048f57be95a408c8cb0716efb28d5a60a76a3f6834b1bd0978f0727a98fd56c33c7e19cb3fffbc4445ff3af37788d9e34fe921f102818100e35c1199c4eb733d406141994c483afa026353bac5a9e191296c0f5b644aa97ebe9edbffff1d82f881d3107bc235e3e4fc3bcad747b8f3c074ab2a89c04f6303d503b920045d79065789310d337ec4c19c1766e5c26b5ec6334a02d75c68dd70543ebe66a266feca70eeb9467ab7e3f6e71ebfd090ab2a1f6050755550c826d702818100b419361fc6110f3beac97abd5c6bcde521eff4153a10e683c4d3a4d54b8113b2b4b4c9dbe24810bb46cba165164083fb12658ac6c1a78ebaaacfc8c9aa3d8dd29e2b40671200d47c9d0559f7999b1e39c3d81f3023e5948c36babe5298a2a8df0aa4f9263f01c032bbaac1be2697649a2d93c4a59f62a58bc93caae9e0aa07fd02818100c1f0043775891df8ce94e75efb79d58cf34284e699bb6a64e7bf7c6e09e3c2271234f216b1281c531c72cce9b46541ab4f1935d26324ba0f9db57008a6d950edb0e387236f75c849879bc812a9ad65ea7cda5d74795e1b2ed80b36d73bc55bb678b638b5d5ac2384cdc664966111c14c1823db9cedfa8368d6406b85c5eda56b028180013dfb4594d4214ac47fc73cef1a3f3d4952a258532232b4cdf2b7bc50dcff09c87d46b7218465b924efda991be22e06d945a20364ea86ed7aa81c3667da2cd43b0b0952aca91cce0f372e3baaef8af1d30bf68b1a7446e600aaa10d68c8166a1364b7e32fa40f734a200f5e045e1eef8148f79e3ccf2d5d1d7231ab934518f102818100d9fcd650864f86e93052b0479e98e978f639baa243ef273a8d4ebe33ac6454c69769c77c76374c3449d88b243092341a97405d1e312b84e764d35140a41afe658051f8ad88fdfb119813cce4d15201e86c34719da511b13d819d96f595987ec69dfaccb2f2b1c8a6e321fc0f38368231d0cdf072c50751b0f74971228b39ae6a" );
		certChains.put("SineKarta",  "308204f3308203dba0030201020206014b2c92016d300d06092a864886f70d01010b0500308191310b30090603550406130249543110300e06035504081307556e6b6e6f776e3110300e06035504071307556e6b6e6f776e31153013060355040a130c494e464f434552542053504131223020060355040b1319436572746966696361746f726520416363726564697461746f312330210603550403131a496e666f43657274204669726d61205175616c69666963617461301e170d3135303132373138303534315a170d3235303132343138303534315a30819c310b300906035504061302495431123010060355040b0c0953696e654b6172746131173015060355040a0c0e4a656e696120536f667477617265311c301a06035504070c13436173616c65636368696f2064692052656e6f311630140603550405130d534b445332303135544930303131163014060355042e130d323031303131313235353731383112301006035504030c0953696e654b6172746130820122300d06092a864886f70d01010105000382010f003082010a02820101009ff3206f7a3eb56f8a5ae3bf2345187f610979da1417f9c7890e17b27be49e50304f680cfa6bcac63f22c416b3551f8c3f98316b62b901aae4afb30d228519dba00ed428ddb6df7569a348cc2e33a221bf86cbfe8e9a8bba76ae2e8c34a19aa2c3b681cbe7383198727b2d119404ff5d857743ca81936f51f5fb77c338e1e1b686945320865f08b62321b4cf7c2f687aaedac653b0e9164861349f9bf7f27f9b0ab2383c534f4d835f1842f6ba3839490765abccb27d5d64107af51ec84fae879977a658e5ae4b4d97b5c3c11948311e79652a30dd2818d78d2af620fd7da2c62b47166a8805313523f340d65b2e9b646dcb9acded010a2d4d6a311b39a4437b0203010001a38201423082013e30090603551d1304023000302f06082b06010505070103042330213008060604008e460101300b060604008e4601030201143008060604008e460104300e0603551d0f0101ff0404030206c0301d0603551d230416041492c9ae14d781fee7c2f7703f9b8257994a6f73953081af0603551d1f0481a73081a43081a1a0819ea0819b8681986c6461703a2f2f6c6461702e696e666f636572742e69742f636e253364496e666f436572742532304669726d612532305175616c6966696361746125323043524c30322c6f75253364436572746966696361746f7265253230416363726564697461746f2c6f253364494e464f434552542532305350412c6325336449543f63657274696669636174655265766f636174696f6e4c697374301f0603551d0e04183016801423f1a24edb41c69d073976421d295a22df24a9b3300d06092a864886f70d01010b05000382010100b2482dd36f79e3a7949f92f1ba2d6376b31b6682f96ac6aeaa22c7e1135e93eb086348132caf11d4021de69cdb2524fe2fb3e42734ecc69a70453cb997a996c878c60ba9591906b82e74c8f58d9de174142f7c591d3ba078f69b9a1171c57bd440c545bcbbeb2eea089f78fc613d65acab7f64200096bee76cf067b352c66582dafd4e024d9b2cb372147230c6647aca5cceda2945222bcdaaa43839c621dce259e65b58d6e083f685f0c6228c85d5c238a1fab1b67b574d3310fc12908b2bcf809c105619eedc003c0a9888a3bf3775d19dd0704a8dab8b56881e94ae9207e9be51e2f421ca960a364dbd34de827842475986eb5c39b4895e50da720355501e" );
//		privateKeys.put("SineKarta", "308204bc020100300d06092a864886f70d0101010500048204a6308204a202010002820101008fab3c544420a6fb20dea410f415eb5f600c0631492b5fdf7f52b1769f5e7befffbc52d06b4e7fa8a893cedf787cdf67fa2e52f204f08c05a312a7799f7aac45802aef0750971675a05c21dc8093d52d5941b307958daf7fc2ce51fa83b3ff793f24e8a978eb4aa17e7e39a97428ea4a83da2a85972ac3591e4562875afbb9b206f697e555f3aa7359bc07060f5269216974a5d5939cfca02d25309300d031b6bae1d3acff9cc8b84898d4fa48d2531a7cb8ee1cb0bc417e2d271bf13908223cc61e94fafc2a4d0ea5967a5bfc91dc2dd8371e44989cd2af53e7e266788dd3e548e5b70489de18ede8a91c5b335f6e957e462fe929ac71ef04ad93b24f5396230203010001028201005c336d9d3ab7a5d226cf1b85d1cfbb6553138469dee65a39e9d15d189ec0995375d11b68ac650d626505aecf15d935dad9e11ed4393d52ef4f2f6a91903abf123f65ce4ee8767e3071008fae9e49c196bfe07d0fd1f7c2f88041beb4c20f235661a2c339f7e330db217dca6da8a19572cad9994e70ac3e0014803cf256fe93b5e86cb12eef9a49e8a166a7519779a760ca9caeb8f32692f9e03bad2437cc6e2f0e0d90fba3748c07af2f83997a207a6383efc9737d94ceb4f77ff896c1312b0ed54fa829bcabe974e45b7c2ddd92f0e75f3a37566d80c041edeeb59d08dd49d04a5c1f12b0da989bf772c013acb16a7735d2d4d03991da02ecb314be8aa2055102818100d4bef9057e38f63fd9fd28d6eb86a66a3a9dcf9aa6cb71edbafd70d0e7cef9636c79a55771ead9385a00ed0be87be81cff59434f19046d9254fabec19f5d1013af4a9f1a5796f2f1e515a2a2a6edd7e5f6699c40c8ddbfdfabae0dfc26fb502753669d653a7bf071c036c17757cb4d554dd9307bd81d510bd4e3be524026b5d902818100ace0ee27dfb524c6631cb097e629200d5d4bc740c98e1e67f31ddcabacb7f911d173168eefa5a1a32dbf65d0eb1d44524e45e7ee154bfb6cdcd92701007dbacc5b325e4176fe9941bded23cca17e81f4f1ac5b38733d645823453ebba881ab2b0678418a39eb86999ae3ec60a91b75d88667f44063f24eb8b8bb9683a3c7425b02818063ea28d3ce9afbf3b2f2358af26473a5b24fc95f8d7440d6709a74b0781df97c1b46ea432e4a28a85184dbcf5ae94e72c7ede931adc3299eaef07044490f10ecddc782ff81217089f097ffa0ba1b3be95197f9fd6718d5f33b12ec987eca4ab39d74b4f904c58e802dd0c97dbe2ac9f00536ba917f470d782c0ca5a6c56028c90281806bd98319a30909e12df1b7057c53c9a82cbcda98436582f88c85e3a5c2019fc38aec0675b3719c0e05bb32d48be8dd378f9732e2a7ac9834c8ee3bd3f72f78ba86663cc1107032c1fdaf9436d3c4ed6416e4d49fb9dce1ddce5b2390c4c5e837d6386d82c33178ee18dffa162f496f525e3f39f475a5b390afc66f56be74cfa302818073000186e374909521e9722bfb780c3c02790053279d878ed624506ac91074be90a4b88c1e52aa15f6db44265b2f70dec85d2b7f252050f5a87c8d85e9b1275cc5e04eae76f049ec966cba4356743f2201af34d283b97cfaf8f98161f1d7780dbe22b78ee9e83312a650970fa894cf86790b468200f5cd8bed07fdaa0e95a6e3" );
//		certChains.put("SineKarta",  "3082043030820318a0030201020206014b2bd00f35300d06092a864886f70d01010b0500307e310b3009060355040613024954310b300906035504081302424f311c301a06035504071313436173616c65636368696f2064692052656e6f31173015060355040a130e4a656e696120536f66747761726531123010060355040b130953696e654b61727461311730150603550403130e4a656e696120536f667477617265301e170d3135303132373134333335305a170d3235303132343134333335305a306c3112301006035504030c0953696e654b6172746131123010060355040b0c0953696e654b6172746131173015060355040a0c0e4a656e696120536f667477617265311c301a06035504070c13436173616c65636368696f2064692052656e6f310b300906035504061302495430820122300d06092a864886f70d01010105000382010f003082010a02820101008fab3c544420a6fb20dea410f415eb5f600c0631492b5fdf7f52b1769f5e7befffbc52d06b4e7fa8a893cedf787cdf67fa2e52f204f08c05a312a7799f7aac45802aef0750971675a05c21dc8093d52d5941b307958daf7fc2ce51fa83b3ff793f24e8a978eb4aa17e7e39a97428ea4a83da2a85972ac3591e4562875afbb9b206f697e555f3aa7359bc07060f5269216974a5d5939cfca02d25309300d031b6bae1d3acff9cc8b84898d4fa48d2531a7cb8ee1cb0bc417e2d271bf13908223cc61e94fafc2a4d0ea5967a5bfc91dc2dd8371e44989cd2af53e7e266788dd3e548e5b70489de18ede8a91c5b335f6e957e462fe929ac71ef04ad93b24f5396230203010001a381c53081c2300e0603551d0f0101ff0404030206403081af0603551d1f0481a73081a43081a1a0819ea0819b8681986c6461703a2f2f6c6461702e696e666f636572742e69742f636e253364496e666f436572742532304669726d612532305175616c6966696361746125323043524c30322c6f75253364436572746966696361746f7265253230416363726564697461746f2c6f253364494e464f434552542532305350412c6325336449543f63657274696669636174655265766f636174696f6e4c697374300d06092a864886f70d01010b050003820101005ccf7ad784476d6356250bd020e28fd8f3f809941986d3c8922fd14a3ffcf735a5815ce8b84f42ddbcf9ecba84cf290d8acee50ff985731f5a1154f0a45c6c71e85dae2b8708b9af6eee08e9cadc465287bdc4ccc38fe2ad97321bc48e89ce858e1effdd8084e0b0f6c18524e39f93b87e6d23972a7e7f65edf06340d1df39f344d935d37dc0b315d8e6f82472a27bb3630d1bbbe4b6e8eb687b479d5bb8e3ad6bbc60df8d2d09affe23a21e6b0373c99c5c583ab2d27069d92847149b8cc590dfd5d02490f0887618a9e3978d520e96e63661ea8a3fc3b08956f9d576b593303eea7080c6b6dcb47008e697918688a348767e51b707a832f0281e2dd5b747e6" );
	}

	public void selectDriver ( String pkcs11Driver ) throws SmartCardReaderNotFoundException, PKCS11DriverNotFoundException, InvalidPKCS11DriverException, InvalidSmartCardException, SmartCardAccessException {
		if ( !StringUtils.equals(pkcs11Driver, FakeSmartCardAccess.FAKE_DRIVER) ) { 
			tracer.error("only \"fake\" driver is allowed");
			throw new InvalidPKCS11DriverException("only \"fake\" driver is allowed");
		}
	}
	
	public String[] loginAndCertificateList ( String pin ) throws IllegalStateException, InvalidPinException, PinLockedException, SmartCardAccessException {
		login(pin);
		return certificateList();
	}
	
	public void login(String pin) throws IllegalStateException, SmartCardAccessException {
		if ( !StringUtils.equals(pin, FakeSmartCardAccess.FAKE_PIN) ) { 
			tracer.error(String.format("pin for the fake smartCard: %s", FAKE_PIN));
			throw new InvalidPinException(String.format("pin for the fake smartCard: %s", FAKE_PIN));
		}
	}

	public String[] certificateList() throws IllegalStateException, SmartCardAccessException {
		return certChains.keySet().toArray(new String[certChains.size()]);
	}

	public X509Certificate selectCertificate(String userAlias) throws CertificateListException {
		
		try {
			privateKey = X509Utils.privateKeyFromHex ( privateKeys.get(userAlias) );
			return X509Utils.rawX509CertificateFromHex ( certChains.get(userAlias) );
		} catch (CertificateException e) {
			tracer.error(String.format("pin for the fake smartCard: %s", FAKE_PIN));
			throw new CertificateListException(e); 
		}
	}

	
	public byte[] signFingerPrint(byte[] fingerPrint) throws IllegalStateException, IllegalArgumentException, SmartCardAccessException {
		
		byte[] digitalSignature;
		try {
			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			byte[] digestInfoValue = ArrayUtils.addAll(
					SHA256_DIGEST_INFO_PREFIX, fingerPrint);
			digitalSignature = cipher.doFinal(digestInfoValue);
		} catch(Exception e) {
			tracer.error("sign fails...",e);
			throw new SmartCardAccessException(e);
		}
		return digitalSignature;
	}

	public void logout() throws IllegalStateException,SmartCardAccessException {
	}
	
	public void open() throws SmartCardAccessException {
		// nothing to do by now
	}

	public void close() throws SmartCardAccessException {
		// nothing to do by now
	}

}
